(function () {
    class Navigator {
        constructor(jsPlumbInstance, options) {
            this.jsPlumbInstance = jsPlumbInstance;
            this.options = options;
            this.canvasEle = this.jsPlumbInstance.defaults.container;
            this.canvasTransform = this.canvasEle.style.transform;
            this.viewportEle = this.canvasEle.offsetParent;
            this.viewportEle.onpointerdown = this.handleViewportPointerDown.bind(this);
            this.viewportEle.onpointerup = this.handleViewportPointerUp.bind(this);
            this.viewportEle.addEventListener('mousedown', this.handleViewportMouseDown.bind(this));
            this.viewportEle.addEventListener('mousewheel', this.handleMiddleMouseScrollAndZoom.bind(this));
            this.jsPlumbInstance.bind('element:manage', this.handleJsPlumbElementManage.bind(this));
            this.jsPlumbInstance.bind('element:unmanage', this.handleJsPlumbElementUnManage.bind(this));
            if (!options.disableMiniMap) {
                this.miniMapEle = this.createMiniMapEle();
                this.viewportEle.appendChild(this.miniMapEle);
                this.miniMap = new MiniMap(this, this.canvasEle, this.miniMapEle);
            }
            if (!options.disableToolbar) {
                this.toolbarEle = this.createToolbarEle();
                this.viewportEle.appendChild(this.toolbarEle);
                this.toolbar = new Toolbar(this, this.toolbarEle);
            }
            this.startAnimationFrame();
            this.bestFit();
        }

        handleViewportMouseDown(e) {
            if (!e.target.closest('[data-jtk-managed]')) {
                this.raiseSelectionChanged();
            }
        }

        handleMiddleMouseScrollAndZoom(e) {
            let scale = this.getCanvasScale();
            if (e.ctrlKey) {
                if (e.wheelDeltaY > 0) {
                    scale += 0.1;
                } else {
                    scale -= 0.1;
                }
                scale = Math.max(0.1, scale);
                scale = Math.min(3, scale);
                this.zoomTo(scale);
            } else {
                let { left, top } = this.getCanvasBounds();
                if (e.wheelDeltaY > 0) {
                    top += 50 / scale;
                } else {
                    top -= 50 / scale;
                }
                this.setCanvasLocationAndScale(left, top, scale);
            }
            e.preventDefault();
            e.stopPropagation();
        }

        handleJsPlumbElementManage(e) {
            if (e.el) {
                e.el.addEventListener('mousedown', this.handleJsPlumbElementMouseDown.bind(this));
            }
        }

        handleJsPlumbElementUnManage(e) {
            if (e.el) {
                e.el.removeEventListener('mousedown', this.handleJsPlumbElementMouseDown.bind(this));
            }
        }

        handleJsPlumbElementMouseDown(e) {
            const element = e.target.closest('[data-jtk-managed]');
            if (element) {
                let elements = this.selectedElements ?? [];
                if (elements.indexOf(element) >= 0) {
                    if (e.ctrlKey) {
                        elements = elements.filter(ele => ele !== element);
                    }
                } else {
                    if (e.ctrlKey) {
                        elements.push(element);
                    } else {
                        elements = [element];
                    }
                }
                this.raiseSelectionChanged(elements);
            }
        }

        handleViewportPointerDown(e) {
            if (e.target === this.viewportEle) {
                let x = 0;
                let y = 0;
                let firstMove = true;
                if (e.ctrlKey) {
                    this.removeBoxSelectionEle();
                    this.boxSelectionStart = { x: e.pageX, y: e.pageY };
                    this.boxSelectionEle = this.createBoxSelectionEle(e.pageX, e.pageY);
                    this.viewportEle.style.cursor = 'crosshair';
                }
                this.viewportEle.onpointermove = e => {
                    if (e.buttons !== 1) {
                        this.viewportEle.onpointermove = null;
                        this.viewportEle.releasePointerCapture(e.pointerId);
                        return;
                    }
                    if (Math.abs(x) > 5 || Math.abs(y) > 5) {
                        if (this.boxSelectionEle) {
                            this.setBoxSelectionBounds(this.boxSelectionStart.x, this.boxSelectionStart.y, e.pageX, e.pageY);
                        } else {
                            this.pan(e.movementX, e.movementY);
                            if (firstMove) {
                                this.viewportEle.style.cursor = 'grabbing';
                                this.viewportEle.setPointerCapture(e.pointerId);
                                firstMove = false;
                            }
                        }
                    } else {
                        x += e.movementX;
                        y += e.movementY;
                    }
                };
            }
        }

        handleViewportPointerUp(e) {
            if (this.boxSelectionEle) {
                const canvasSelectionBounds = this.getCanvasSelectionBounds();
                this.raiseSelectionChanged(this.getSelectedElementsByBounds(canvasSelectionBounds));
                this.removeBoxSelectionEle();
            }
            this.viewportEle.onpointermove = null;
            this.viewportEle.releasePointerCapture(e.pointerId);
            this.viewportEle.style.cursor = null;
        }

        createBoxSelectionEle(x, y) {
            const ele = document.createElement('div');
            ele.className = 'jsplumb-box-selection';
            ele.style.position = 'absolute';
            ele.style.left = `${x}px`;
            ele.style.top = `${y}px`;
            ele.style.userSelect = 'none';
            ele.style.pointerEvents = 'none';
            ele.style.border = '1px dashed #000';
            ele.style.backgroundColor = '#00000010';
            ele.style.zIndex = '999999999';
            document.body.appendChild(ele);
            return ele;
        }

        getCanvasSelectionBounds() {
            if (this.boxSelectionEle) {
                const boxSelectionBounds = this.boxSelectionEle.getBoundingClientRect();
                const scale = this.getCanvasScale();
                const viewportBounds = this.viewportEle.getBoundingClientRect();
                const canvasVisualBounds = this.getCanvasVisualBounds(scale);
                return {
                    left: canvasVisualBounds.left + (boxSelectionBounds.left - viewportBounds.left) / scale,
                    top: canvasVisualBounds.top + (boxSelectionBounds.top - viewportBounds.top) / scale,
                    width: boxSelectionBounds.width / scale,
                    height: boxSelectionBounds.height / scale,
                };
            };
        }

        setBoxSelectionBounds(left, top, right, bottom) {
            if (this.boxSelectionEle) {
                this.boxSelectionEle.style.left = `${Math.min(left, right)}px`;
                this.boxSelectionEle.style.top = `${Math.min(top, bottom)}px`;
                this.boxSelectionEle.style.width = `${Math.abs(right - left)}px`;
                this.boxSelectionEle.style.height = `${Math.abs(bottom - top)}px`;
            }
        }

        raiseSelectionChanged(elements) {
            elements ??= [];
            this.selectedElements ??= [];
            if (this.selectedElements.length !== 0 || elements.length !== 0) {
                this.selectedElements = elements;
                this.jsPlumbInstance.clearDragSelection();
                for (const element of elements) {
                    this.jsPlumbInstance.addToDragSelection(element);
                }
                if (this.options.selectionChanged) {
                    this.options.selectionChanged(elements);
                }
            }
        }

        getSelectedElementsByBounds(bounds) {
            const selectedElements = [];
            const elements = this.jsPlumbInstance.getManagedElements();
            for (const key in elements) {
                const ele = elements[key].el;
                const left = ele.offsetLeft;
                const top = ele.offsetTop;
                const right = left + ele.offsetWidth;
                const bottom = top + ele.offsetHeight;
                if (left < bounds.left + bounds.width
                    && right > bounds.left
                    && top < bounds.top + bounds.height
                    && bottom > bounds.top) {
                    selectedElements.push(ele);
                }
            }
            return selectedElements;
        }

        removeBoxSelectionEle() {
            if (this.boxSelectionEle) {
                document.body.removeChild(this.boxSelectionEle);
                this.boxSelectionStart = null;
                this.boxSelectionEle = null;
            }
        }

        createMiniMapEle() {
            const { miniMapWidth, miniMapHeight, miniMapPosition } = this.options;
            const ele = document.createElement('div');
            ele.className = 'minimap';
            ele.style.position = 'absolute';
            ele.style.width = `${miniMapWidth}px`;
            ele.style.height = `${miniMapHeight}px`;
            ele.style.overflow = 'hidden';
            ele.style.backgroundColor = '#eee';
            const margin = '4px';
            switch (miniMapPosition) {
                case 'top-left':
                    ele.style.left = margin;
                    ele.style.top = margin;
                    break;
                case 'top-right':
                    ele.style.right = margin;
                    ele.style.top = margin;
                    break;
                case 'bottom-left':
                    ele.style.left = margin;
                    ele.style.bottom = margin;
                    break;
                default:
                    ele.style.right = margin;
                    ele.style.bottom = margin;
                    break;
            }
            return ele;
        }

        createToolbarEle() {
            const ele = document.createElement('div');
            ele.className = 'toolbar';
            ele.style.position = 'absolute';
            ele.style.overflow = 'hidden';
            ele.style.boxShadow = '0 0 2px 1px rgba(0, 0, 0, 0.08)';
            const margin = '4px';
            switch (this.options.toolbarPosition) {
                case 'top-left':
                    ele.style.left = margin;
                    ele.style.top = margin;
                    break;
                case 'top-right':
                    ele.style.right = margin;
                    ele.style.top = margin;
                    break;
                case 'bottom-right':
                    ele.style.right = margin;
                    ele.style.bottom = margin;
                    break;
                default:
                    ele.style.left = margin;
                    ele.style.bottom = margin;
                    break;
            }
            return ele;
        }

        setCanvasTransform() {
            if (this.canvasTransform && this.canvasTransform !== this.canvasEle.style.transform) {
                this.canvasEle.style.transform = this.canvasTransform;
            }
            if (!this.isDisposed) {
                this.setCanvasTransformFrameHandle = window.requestAnimationFrame(() => this.setCanvasTransform());
            }
        }

        startAnimationFrame() {
            this.setCanvasTransform();
        }

        stopAnimationFrame() {
            this.canvasTransform = undefined;
            if (this.setCanvasTransformFrameHandle) {
                window.cancelAnimationFrame(this.setCanvasTransformFrameHandle);
            }
        }

        getElementsBounds() {
            let left, right, top, bottom;
            const elements = this.jsPlumbInstance.getManagedElements();
            for (const key in elements) {
                const ele = elements[key].el;
                const offset = { left: ele.offsetLeft, top: ele.offsetTop };
                const size = { width: ele.offsetWidth, height: ele.offsetHeight };
                left = left === undefined ? offset.left : Math.min(left, offset.left);
                top = top === undefined ? offset.top : Math.min(top, offset.top);
                right = right === undefined ? offset.left + size.width : Math.max(right, offset.left + size.width);
                bottom = bottom === undefined ? offset.top + size.height : Math.max(bottom, offset.top + size.height);
            }
            return {
                left: left || 0,
                top: top || 0,
                width: right - left || 0,
                height: bottom - top || 0,
            };
        }

        getCanvasScale() {
            let scale = 1;
            const transform = this.canvasTransform;
            if (transform) {
                const index = transform.indexOf('scale(');
                if (index > -1) {
                    scale = parseFloat(transform.substring(index + 6, transform.indexOf(')', index)));
                }
            }
            return scale;
        }

        getCanvasBounds() {
            let left = 0;
            let top = 0;
            const transform = this.canvasTransform;
            if (transform) {
                const index = transform.indexOf('translate(');
                if (index > -1) {
                    const offset = transform.substring(index + 10, transform.indexOf(')', index));
                    left = parseFloat(offset.split(',')[0]);
                    top = parseFloat(offset.split(',')[1]);
                }
            }
            return {
                left,
                top,
                width: this.canvasEle.offsetWidth,
                height: this.canvasEle.offsetHeight,
            };
        }

        getCanvasVisualBounds(scale) {
            const canvasBounds = this.getCanvasBounds();
            return {
                left: -canvasBounds.left,
                top: -canvasBounds.top,
                width: this.viewportEle.offsetWidth / scale,
                height: this.viewportEle.offsetHeight / scale,
            };
        }

        getCanvasActualBounds() {
            const canvasBounds = this.getCanvasBounds();
            const canvasVisualBounds = this.getElementsBounds();
            return {
                left: canvasBounds.left + canvasVisualBounds.left,
                top: canvasBounds.top + canvasVisualBounds.top,
                width: canvasVisualBounds.width,
                height: canvasVisualBounds.height,
            };
        }

        setCanvasLocation(left, top, force) {
            this.setCanvasLocationAndScale(left, top, this.getCanvasScale(), force);
        }

        setCanvasLocationAndScale(left, top, scale, force) {
            this.canvasTransform = `scale(${scale}) translate(${left}px, ${top}px)`;
            if (force) {
                this.canvasEle.style.transform = this.canvasTransform;
            }
            if (this.options.viewportUpdated) {
                this.options.viewportUpdated(this.getCanvasVisualBounds(scale));
            }
        }

        getCanvasScaleBounds() {
            const scale = this.getCanvasScale();
            const bounds = this.getCanvasBounds();
            return {
                left: bounds.left,
                top: bounds.top,
                width: bounds.width * scale,
                height: bounds.height * scale,
            };
        }

        getViewportBounds() {
            return {
                left: 0,
                top: 0,
                width: this.viewportEle.offsetWidth,
                height: this.viewportEle.offsetHeight,
            };
        }

        viewportPointToCanvans(point) {
            const canvasBounds = this.getCanvasBounds();
            const canvasScale = this.getCanvasScale();
            return {
                x: point.x / canvasScale - canvasBounds.left,
                y: point.y / canvasScale - canvasBounds.top,
            };
        }

        pan(offsetX, offsetY) {
            const scale = this.getCanvasScale();
            const bounds = this.getCanvasBounds();
            const left = bounds.left + offsetX / scale;
            const top = bounds.top + offsetY / scale;
            this.setCanvasLocation(left, top);
        }

        zoomTo(scale, viewportPoint) {
            const viewportBounds = this.getViewportBounds();
            viewportPoint = viewportPoint ?? {
                x: viewportBounds.left + viewportBounds.width / 2,
                y: viewportBounds.top + viewportBounds.height / 2,
            };
            const canvasPoint = this.viewportPointToCanvans(viewportPoint);
            const left = viewportPoint.x - canvasPoint.x * scale;
            const top = viewportPoint.y - canvasPoint.y * scale;
            this.setCanvasLocationAndScale(left / scale, top / scale, scale);
            this.jsPlumbInstance.setZoom(scale);
        }

        zoomIn() {
            this.zoomTo(Math.min(3, this.getCanvasScale() + 0.1));
        }

        zoomOut() {
            this.zoomTo(Math.max(0.1, this.getCanvasScale() - 0.1));
        }

        zoomTo100() {
            this.zoomTo(1);
        }

        bestFit() {
            const viewportBounds = this.getViewportBounds();
            const canvasBounds = this.getCanvasBounds();
            const canvasActualBounds = this.getCanvasActualBounds();
            let scale = viewportBounds.width / canvasActualBounds.width;
            scale = Math.min(1, scale);
            scale = Math.max(0.5, scale);
            const width = canvasActualBounds.width * scale;
            const height = canvasActualBounds.height * scale;
            const left = viewportBounds.left + (viewportBounds.width - width) / 2 - (canvasActualBounds.left - canvasBounds.left) * scale;
            let top = 0;
            if (viewportBounds.height > height) {
                top = viewportBounds.top + (viewportBounds.height - height) / 2 - (canvasActualBounds.top - canvasBounds.top) * scale;
            } else {
                top = viewportBounds.top - (canvasActualBounds.top - canvasBounds.top) * scale;
            }
            this.setCanvasLocationAndScale(left / scale, top / scale, scale, true);
            this.jsPlumbInstance.setZoom(scale);
            return scale;
        }

        fitToViewport() {
            const viewportBounds = this.getViewportBounds();
            const canvasBounds = this.getCanvasBounds();
            const canvasActualBounds = this.getCanvasActualBounds();
            let scale = 1;
            if (viewportBounds.width / viewportBounds.height > canvasActualBounds.width / canvasActualBounds.height) {
                scale = viewportBounds.height / canvasActualBounds.height;
            } else {
                scale = viewportBounds.width / canvasActualBounds.width;
            }
            const width = canvasActualBounds.width * scale;
            const height = canvasActualBounds.height * scale;
            const left = viewportBounds.left + (viewportBounds.width - width) / 2 - (canvasActualBounds.left - canvasBounds.left) * scale;
            const top = viewportBounds.top + (viewportBounds.height - height) / 2 - (canvasActualBounds.top - canvasBounds.top) * scale;
            this.setCanvasLocationAndScale(left / scale, top / scale, scale, true);
            this.jsPlumbInstance.setZoom(scale);
            return scale;
        }

        autoLayout() {
            const nodes = [];

            const elements = this.jsPlumbInstance.getManagedElements();
            for (const key in elements) {
                const el = elements[key].el;
                nodes.push({
                    id: el.id,
                    width: el.offsetWidth,
                    height: el.offsetHeight
                });
            }

            const edges = this.jsPlumbInstance.getConnections().map(conn => {
                return {
                    id: conn.id,
                    source: conn.source.id,
                    target: conn.target.id,
                };
            });

            var elk = new ELK();

            elk.layout({
                id: "root",
                layoutOptions: {
                    'elk.algorithm': 'layered',
                    'elk.direction': 'DOWN',
                    'elk.spacing.nodeNode': '150',
                    'elk.spacing.edgeNode': '100',
                    'elk.layered.spacing.nodeNodeBetweenLayers': '150',
                },
                children: nodes,
                edges: edges
            }).then(layout => {
                const nodes = layout.children.map(node => {
                    return {
                        id: node.id,
                        x: node.x,
                        y: node.y,
                    };
                });

                nodes.forEach(node => {
                    var el = document.getElementById(node.id);
                    el.style.left = node.x + "px";
                    el.style.top = node.y + "px";
                    this.jsPlumbInstance.revalidate(el);
                });

                if (this.options.nodesLayouted) {
                    this.options.nodesLayouted(nodes);
                }
            });
        }

        getMiniMapVisible() {
            return this.miniMapEle && this.miniMapEle.style.display !== 'none';
        }

        showMiniMap() {
            if (this.miniMapEle && this.miniMap) {
                this.miniMapEle.style.display = 'block';
                this.miniMap.refresh(true);
            }
        }

        hideMiniMap() {
            if (this.miniMapEle) {
                this.miniMapEle.style.display = 'none';
            }
        }

        getToolbarVisible() {
            return this.toolbarEle && this.toolbarEle.style.display !== 'none';
        }

        showToolbar() {
            if (this.toolbarEle) {
                this.toolbarEle.style.display = 'block';
            }
        }

        hideToolbar() {
            if (this.toolbarEle) {
                this.toolbarEle.style.display = 'none';
            }
        }

        dispose() {
            this.stopAnimationFrame();
            this.miniMap?.dispose();
            this.viewportEle.onpointerdown = null;
            this.viewportEle.onpointermove = null;
            this.viewportEle.onpointerup = null;
            this.viewportEle.removeEventListener('mousedown', this.handleViewportMouseDown);
            this.viewportEle.removeEventListener('mousewheel', this.middleMouseZoomHandle);
            this.jsPlumbInstance.unbind('element:manage', this.handleJsPlumbElementManage);
            this.jsPlumbInstance.unbind('element:unmanage', this.handleJsPlumbElementUnManage);
            this.isDisposed = true;
        }
    }

    class MiniMap {
        constructor(navigator, canvasEle, miniMapEle) {
            this.navigator = navigator;
            this.canvasEle = canvasEle;
            this.miniMapEle = miniMapEle;
            this.miniMapViewportEle = this.createMiniMapViewportEle();
            this.miniMapCanvasEle = this.createMiniMapCanvasEle();
            this.miniMapViewportEle.appendChild(this.miniMapCanvasEle);
            this.miniMapEle.appendChild(this.miniMapViewportEle);
            this.miniMapEle.addEventListener('mousewheel', this.handleMiddleMouseZoom.bind(this));
            this.miniMapEle.onpointerdown = this.handlePointerDown.bind(this);
            this.miniMapEle.onpointerup = this.handlePointerUp.bind(this);
            this.refresh(true);
        }

        handleMiddleMouseZoom(e) {
            let scale = this.navigator.getCanvasScale();
            if (e.wheelDeltaY > 0) {
                scale += 0.1;
            } else {
                scale -= 0.1;
            }
            scale = Math.max(0.1, scale);
            scale = Math.min(3, scale);
            this.navigator.zoomTo(scale);
            this.layout();
            e.preventDefault();
            e.stopPropagation();
        }

        handlePointerDown(e) {
            this.miniMapEle.onpointermove = this.handlePointerMove.bind(this);
            this.miniMapEle.style.cursor = 'grabbing';
            this.miniMapEle.setPointerCapture(e.pointerId);
        }

        handlePointerMove(e) {
            if (e.buttons !== 1) {
                this.miniMapEle.onpointermove = null;
                this.miniMapEle.releasePointerCapture(e.pointerId);
                return;
            }
            this.navigator.pan(-e.movementX / this.viewScale, -e.movementY / this.viewScale);
            this.layout();
        }

        handlePointerUp(e) {
            this.miniMapEle.onpointermove = null;
            this.miniMapEle.releasePointerCapture(e.pointerId);
            this.miniMapEle.style.cursor = 'grab';
        }

        layout() {
            const viewportBounds = this.navigator.getViewportBounds();
            const canvasActualBounds = this.navigator.getCanvasActualBounds();
            const canvasScale = this.navigator.getCanvasScale();
            const globalBounds = {
                left: Math.min(viewportBounds.left, canvasActualBounds.left * canvasScale),
                top: Math.min(viewportBounds.top, canvasActualBounds.top * canvasScale),
                right: Math.max(viewportBounds.left + viewportBounds.width, (canvasActualBounds.left + canvasActualBounds.width) * canvasScale),
                bottom: Math.max(viewportBounds.top + viewportBounds.height, (canvasActualBounds.top + canvasActualBounds.height) * canvasScale),
            };
            globalBounds.width = globalBounds.right - globalBounds.left;
            globalBounds.height = globalBounds.bottom - globalBounds.top;
            const miniMapBounds = {
                left: 4,
                top: 4,
                width: this.miniMapEle.offsetWidth - 8,
                height: this.miniMapEle.offsetHeight - 8,
            };
            const viewBounds = {};
            if (miniMapBounds.width / miniMapBounds.height > globalBounds.width / globalBounds.height) {
                viewBounds.height = miniMapBounds.height;
                viewBounds.width = viewBounds.height * (globalBounds.width / globalBounds.height);
                viewBounds.left = miniMapBounds.left + (miniMapBounds.width - viewBounds.width) / 2;
                viewBounds.top = miniMapBounds.top;
            } else {
                viewBounds.width = miniMapBounds.width;
                viewBounds.height = viewBounds.width / (globalBounds.width / globalBounds.height);
                viewBounds.left = miniMapBounds.left;
                viewBounds.top = miniMapBounds.top + (miniMapBounds.height - viewBounds.height) / 2;
            }
            this.viewScale = viewBounds.width / globalBounds.width;
            this.miniMapViewportEle.style.left = `${viewBounds.left}px`;
            this.miniMapViewportEle.style.top = `${viewBounds.top}px`;
            this.miniMapViewportEle.style.width = `${viewportBounds.width}px`;
            this.miniMapViewportEle.style.height = `${viewportBounds.height}px`;
            this.miniMapViewportEle.style.transform = `scale(${this.viewScale}) translate(${-globalBounds.left}px, ${-globalBounds.top}px)`;
            this.miniMapCanvasEle.style.transform = this.canvasEle.style.transform;
        }

        getElementsBoundsString() {
            if (!this.lastGetElementsBoundsStringTime || Date.now() - this.lastGetElementsBoundsStringTime > 200) {
                this.cacheElementsBoundsString = '';
                const elements = this.navigator.jsPlumbInstance.getManagedElements();
                for (const key in elements) {
                    const ele = elements[key].el;
                    const left = parseInt(ele.offsetLeft);
                    const top = parseInt(ele.offsetTop);
                    const width = parseInt(ele.offsetWidth);
                    const height = parseInt(ele.offsetHeight);
                    this.cacheElementsBoundsString += `${left},${top},${width},${height},`;
                }
                this.lastGetElementsBoundsStringTime = Date.now();
            }
            return this.cacheElementsBoundsString;
        }

        createMiniMapViewportEle() {
            const ele = document.createElement('div');
            ele.className = 'minimap-viewport';
            ele.style.position = 'absolute';
            ele.style.transformOrigin = 'left top';
            ele.style.backgroundColor = 'white';
            return ele;
        }

        createMiniMapCanvasEle() {
            const ele = document.createElement('div');
            ele.className = 'minimap-canvas';
            ele.style.position = 'absolute';
            ele.style.transformOrigin = 'left top';
            return ele;
        }

        createElementOutlineEle(bounds) {
            const ele = document.createElement('div');
            ele.className = 'minimap-element-outline';
            ele.style.position = 'absolute';
            ele.style.left = `${bounds.left}px`;
            ele.style.top = `${bounds.top}px`;
            ele.style.width = `${bounds.width}px`;
            ele.style.height = `${bounds.height}px`;
            ele.style.backgroundColor = '#00000050';
            return ele;
        }

        drawCanvasOutline() {
            this.miniMapCanvasEle.innerHTML = '';
            const fragment = document.createDocumentFragment();
            const elements = this.navigator.jsPlumbInstance.getManagedElements();
            for (const key in elements) {
                const ele = elements[key].el;
                const bounds = { left: ele.offsetLeft, top: ele.offsetTop, width: ele.offsetWidth, height: ele.offsetHeight };
                fragment.appendChild(this.createElementOutlineEle(bounds));
            }
            this.miniMapCanvasEle.appendChild(fragment);
        }

        refresh(force) {
            if (this.miniMapEle.style.display !== 'none') {
                let redraw = false;
                let relayout = false;
                const elementsBoundsString = this.getElementsBoundsString();
                if (elementsBoundsString !== this.elementsBoundsString) {
                    this.elementsBoundsString = elementsBoundsString;
                    redraw = true;
                    relayout = true;
                }
                if (this.canvasEle.style.transform !== this.canvasEleTransform) {
                    this.canvasEleTransform = this.canvasEle.style.transform;
                    relayout = true;
                }
                if (relayout || force) {
                    if (redraw) {
                        this.drawCanvasOutline();
                    }
                    this.layout();
                }
                if (!this.isDisposed) {
                    this.animationFrameHandle = window.requestAnimationFrame(() => this.refresh());
                }
            }
        }

        dispose() {
            window.cancelAnimationFrame(this.animationFrameHandle);
            this.miniMapEle.onpointerdown = null;
            this.miniMapEle.onpointermove = null;
            this.miniMapEle.onpointerup = null;
            this.miniMapEle.removeEventListener('mousewheel', this.handleMiddleMouseZoom);
            this.isDisposed = true;
        }
    }

    class Toolbar {
        zoomInSvg = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#666"><path d="M637 443H519V309c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v134H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h118v134c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V519h118c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="8525"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="8526"></path></svg>`;
        zoomOutSvg = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#666"><path d="M637 443H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h312c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="8665"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="8666"></path></svg>`;
        zoomTo100Svg = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#666"><path d="M316 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8zM512 622c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39zM512 482c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39z" p-id="8805"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="8806"></path><path d="M648 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8z" p-id="8807"></path></svg>`;
        bestFitSvg = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#666"><path d="M326 664H104c-8.8 0-16 7.2-16 16v48c0 8.8 7.2 16 16 16h174v176c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V696c0-17.7-14.3-32-32-32zM342 88h-48c-8.8 0-16 7.2-16 16v176H104c-8.8 0-16 7.2-16 16v48c0 8.8 7.2 16 16 16h222c17.7 0 32-14.3 32-32V104c0-8.8-7.2-16-16-16zM920 664H698c-17.7 0-32 14.3-32 32v224c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V744h174c8.8 0 16-7.2 16-16v-48c0-8.8-7.2-16-16-16zM920 280H746V104c0-8.8-7.2-16-16-16h-48c-8.8 0-16 7.2-16 16v224c0 17.7 14.3 32 32 32h222c8.8 0 16-7.2 16-16v-48c0-8.8-7.2-16-16-16z" p-id="9363"></path></svg>`;
        fitToViewportSvg = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#666"><path d="M342 88H120c-17.7 0-32 14.3-32 32v224c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V168h174c8.8 0 16-7.2 16-16v-48c0-8.8-7.2-16-16-16zM920 664h-48c-8.8 0-16 7.2-16 16v176H682c-8.8 0-16 7.2-16 16v48c0 8.8 7.2 16 16 16h222c17.7 0 32-14.3 32-32V680c0-8.8-7.2-16-16-16zM342 856H168V680c0-8.8-7.2-16-16-16h-48c-8.8 0-16 7.2-16 16v224c0 17.7 14.3 32 32 32h222c8.8 0 16-7.2 16-16v-48c0-8.8-7.2-16-16-16zM904 88H682c-8.8 0-16 7.2-16 16v48c0 8.8 7.2 16 16 16h174v176c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V120c0-17.7-14.3-32-32-32z" p-id="9224"></path></svg>`;
        autoLayoutSvg = `<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="31707" width="16" height="16" fill="#666"><path d="M671.232 1019.072h288.192a64 64 0 0 0 64-64v-218.816a64 64 0 0 0-64-64h-115.2v-138.88a64 64 0 0 0-64-64H554.624V324.16h149.888a64 64 0 0 0 64-64V64.448a64 64 0 0 0-64-64H346.88a64 64 0 0 0-64 64v195.712a64 64 0 0 0 64 64h149.888v145.216H264.32a64 64 0 0 0-64 64v138.88h-115.2a64 64 0 0 0-64 64v218.816a64 64 0 0 0 64 64h288.192a64 64 0 0 0 64-64v-218.816a64 64 0 0 0-64-64h-115.2V527.168h528.32v145.088h-115.2a64 64 0 0 0-64 64v218.816a64 64 0 0 0 64 64zM340.736 234.368V90.24a32 32 0 0 1 32-32h305.92a32 32 0 0 1 32 32v144.128a32 32 0 0 1-32 32h-305.92a32 32 0 0 1-32-32z m38.784 495.68v231.232H78.912v-231.232H379.52z m586.112 32v167.232a32 32 0 0 1-32 32H696.96a32 32 0 0 1-32-32v-167.232a32 32 0 0 1 32-32h236.608a32 32 0 0 1 32 32z" p-id="31708"></path></svg>`;

        constructor(navigator, toolbarEle) {
            this.navigator = navigator;
            this.toolbarEle = toolbarEle;
            this.toolbarEle.appendChild(this.createTool('放大', this.zoomInSvg, this.navigator.zoomIn.bind(this.navigator)));
            this.toolbarEle.appendChild(this.createTool('缩小', this.zoomOutSvg, this.navigator.zoomOut.bind(this.navigator)));
            this.toolbarEle.appendChild(this.createTool('100%', this.zoomTo100Svg, this.navigator.zoomTo100.bind(this.navigator)));
            this.toolbarEle.appendChild(this.createTool('最佳适应', this.bestFitSvg, this.navigator.bestFit.bind(this.navigator)));
            this.toolbarEle.appendChild(this.createTool('适应画布', this.fitToViewportSvg, this.navigator.fitToViewport.bind(this.navigator), true));
            this.toolbarEle.appendChild(this.createTool('自动布局', this.autoLayoutSvg, this.navigator.autoLayout.bind(this.navigator), true));
        }

        createTool(title, image, click, last) {
            const button = document.createElement('button');
            button.className = 'tool';
            button.title = title;
            button.innerHTML = image;
            button.onclick = click;
            button.style.width = '16px';
            button.style.height = '16px';
            button.style.padding = '5px';
            button.style.boxSizing = 'content-box';
            button.style.display = 'flex';
            button.style.alignItems = 'center';
            button.style.justifyContent = 'center';
            button.style.border = 'none';
            if (!last) {
                button.style.borderBottom = '1px solid #eee';
            }
            button.style.backgroundColor = 'white';
            button.style.cursor = 'pointer';
            button.style.userSelect = 'none';
            button.onmouseover = () => {
                button.style.backgroundColor = '#f0f0f0';
            };
            button.onmouseout = () => {
                button.style.backgroundColor = 'white';
            };
            return button;
        }
    }

    window.createJsPlumbNavigator = (jsPlumbInstance, options) => {
        const defaultOptions = {
            miniMapWidth: 200,
            miniMapHeight: 150,
            miniMapPosition: 'bottom-right',
            toolbarPosition: 'bottom-left',
            disableMiniMap: false,
            disableToolbar: false,
        };
        const navigator = new Navigator(jsPlumbInstance, { ...defaultOptions, ...options });
        return {
            getZoom: navigator.getCanvasScale.bind(navigator),
            zoomTo: navigator.zoomTo.bind(navigator),
            zoomIn: navigator.zoomIn.bind(navigator),
            zoomOut: navigator.zoomOut.bind(navigator),
            zoomTo100: navigator.zoomTo100.bind(navigator),
            bestFit: navigator.bestFit.bind(navigator),
            fitToViewport: navigator.fitToViewport.bind(navigator),
            getMiniMapVisible: navigator.getMiniMapVisible.bind(navigator),
            showMiniMap: navigator.showMiniMap.bind(navigator),
            hideMiniMap: navigator.hideMiniMap.bind(navigator),
            getToolbarVisible: navigator.getToolbarVisible.bind(navigator),
            showToolbar: navigator.showToolbar.bind(navigator),
            hideToolbar: navigator.hideToolbar.bind(navigator),
            dispose: navigator.dispose.bind(navigator),
        };
    };
})();
